close

這篇文章有點長,步驟有點煩雜,覺得累的話請先喝杯水再來。

在 C/C++ 裡面,有些函式提供了很人性化的介面機制,叫 function pointer - 函式指標,只要在 MSDN 上原型裡面,參數看到是 PROC 結尾的,幾乎都是函式指標。只要是在 API 裡面出現的函式指標,那跟 struct 沒什麼二樣,有一定的函式規格,還要再去查這個函式指標的原型長怎樣。

我們以 EnumWindow 為例,這個 API 函式主要是在列舉目前 Windows 上的可見視窗,它的原型如下

BOOL WINAPI EnumWindows(
  __in  WNDENUMPROC lpEnumFunc,
  __in  LPARAM lParam
);

注意到了,第一個參數就是所謂的函式指標,這裡的用法有點步驟,等一下直接看範例碼會比較快
第二個參數 lParam 提供一些相關資訊。

剛說過了,API 裡面出現了函式指標了,於是我們再上 MSDN 去查 WNDENUMPROC lpEnumFunc 它的規格長怎樣,原型如下

BOOL CALLBACK EnumWindowsProc(
  __in  HWND hwnd,
  __in  LPARAM lParam
);

嗯,這個看起來簡單很多,第一個 HWND hwnd 是視窗 handle,第二個 lParam ,這又是提供另一些資料,我們不會用到。但要注意,這裡有傳回值是 BOOL。

好了,接下來用 autoit 去做會有一些步驟..
這裡先弄清楚,我們真正要用的是 EnumWindows(A) 這個函式,
但 EnumWindows 必須先自己寫一個 "符合標準,但是要自己寫的" 
CALLBACK EnumWindowsProc (B) 函式,等於是要分二層,
先把裡面的 (B) 處理好,才能讓 (A) 去使用,於是要先處理的,是 CALLBACK 函式 - EnumWindowsProc

// ---------------------------------------------------------------------
// 步驟一  針對 EnumWindowsProc 寫一個自己的處理函式

EnumWindowsProc 這 function 型態輸入引數是 hwnd, lParam,傳回 BOOL我們 function 也不去動它,自己先寫一個 myEnumWindowProc,一開始會長得像這樣

; 自己的 CALLBACK 函式
Function myEnumWindowProc($hwnd, $lParam)

EndFunc

再來,剛說過了,這個函式的引數第一個是視窗的 handle ,第二個參數我們不在意,我們這自己寫的程式寫簡單一點,只要去秀出這個視窗的抬頭文字是什麼。 autoit 裡面,有視窗 handle 要取得它的文字是用 WinGetTitle,於是我們函式就變成這樣

; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
     MsgBox(0, "", WinGetTitle($hWnd))
     return 1
EndFunc

嗯,這裡要跟各位說,上面這個看起來好像沒問題,其實問題很大。因為 EnumWindows 這個函式有用到另一個函式 -  EnumWindowsProc ,它會一直不斷不斷不斷不斷... 地一直找下一個視窗,直到myWindowProc 傳回0為止。意思是如果我上面一直傳回 1 的話, EnumWindowsProc 就會找不停,可能會找到當為止。所以必須再給它適合的終止條件

; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
     If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
          $res = MsgBox(1, "", WinGetTitle($hWnd))
          If $res = 2 Then Return 0   ; 取消點選,返回 0 結束列出動作
    EndIf
    Return 1    ; 返回 1 繼續列出

EndFunc

// ---------------------------------------------------------------------
// 步驟二  為自己新增的 CALLBACK  函式進行註冊

要使用上述自己的 CALLBACK 函式之前,記得用AutoIt 向 dll 註冊函式,註冊的時候再看一次 API 的原型

BOOL CALLBACK EnumWindowsProc(
  __in  HWND hwnd,
  __in  LPARAM lParam
);

傳回 BOOL(對應用 autoit 可以用 int 帶過),第一個是 hwnd,第二個是 lParam。於是我們使用 DllCallbackRegister 函式為 CALLBACK 系列的 API 進行註冊

$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam") 
$FuncHandle = DllCallbackRegister ("自己寫好的參數名稱", "API 傳回形態", "API 參數1;API參數2;...")

 於是現在整段程式碼就變這樣

; 註冊自己的 CALLBACK 函式
$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam") 

; .....
; 自己的 CALLBACK 函式

Func myWindowProc($hWnd, $lParam)
     If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
          $res = MsgBox(1, "", WinGetTitle($hWnd))
          If $res = 2 Then Return 0   ; 取消點選,返回 0 結束列出動作
    EndIf
    Return 1    ; 返回 1 繼續列出

EndFunc

 

// ---------------------------------------------------------------------
// 步驟三  使用 DllCall 呼叫 EnumWindows API

CALLBACK 函式都寫好、註冊完了,接下來總算可以叫 EnumWindows API 幫我們做事了。再看一次 EnumWindows 原型

BOOL WINAPI EnumWindows(
  __in  WNDENUMPROC lpEnumFunc,
  __in  LPARAM lParam
);

傳回 BOOL (autoit 用 it),參數一是我們剛剛寫好、註冊好的 $FundHandle,這裡要注意,之前有說過,只要是「指標」,我們的資料型態都用 "ptr",而變數一律要再調用 "DllCallbackGetPtr" 去取得該資料的位址(因為是指標,是傳送位址值),這裡也是一樣,只要是 CALLBACK ,就是函式指標,所以我們要傳的是 "一個函式的指標"。參數二直接用 lParam, 這個放什麼都沒差,直接塞一個垃圾也行。寫出來就像下面這樣

; 註冊自己的 CALLBACK 函式
$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam") 

; 向 Dll 呼叫 EnumWindows,CALLBACK 函式使用自己寫的
DllCall("user32.dll", "int", "EnumWindows", _
           "ptr", DllCallbackGetPtr($FuncHandle), "lparam", 0 )


; 自己的 CALLBACK 函式

Func myWindowProc($hWnd, $lParam)
     If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
          $res = MsgBox(1, "", WinGetTitle($hWnd))
          If $res = 2 Then Return 0   ; 取消點選,返回 0 結束列出動作
    EndIf
    Return 1    ; 返回 1 繼續列出

EndFunc

// ---------------------------------------------------------------------
// 步驟四  最後再把已註冊的 CALLBACK 函式釋放掉

最後一個步驟是用 DllCallbackFree ,把我們剛剛註冊好的 $FuncHandle 釋放掉。整段完整原始碼如下 

#include <GUIConstantsEx.au3>

; 註冊自己的 CALLBACK 函式

$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam") 

; 向 Dll 呼叫 EnumWindows,CALLBACK 函式使用自己寫的
DllCall("user32.dll", "int", "EnumWindows", _
           "ptr", DllCallbackGetPtr($FuncHandle), "lparam", 0 )

; 釋放 callback 函數
DllCallbackFree($FuncHandle)


; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
     If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
          $res = MsgBox(1, "", WinGetTitle($hWnd))
          If $res = 2 Then Return 0   ; 取消點選,返回 0 結束列出動作
    EndIf
    Return 1    ; 返回 1 繼續列出

EndFunc

這部份比較煩雜,如果有問題的話,可以先去看前面幾篇文章,這篇再看個二、三次應該就可以了。

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 edisonx 的頭像
    edisonx

    Edison.X. Blog

    edisonx 發表在 痞客邦 留言(1) 人氣()